home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Games of Daze
/
Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso
/
x2ftp
/
msdos
/
mxutil
/
tspak17
/
recorder.asm
< prev
next >
Wrap
Assembly Source File
|
1994-09-09
|
15KB
|
587 lines
; RECORDER.ASM
;
; This file contains external Turbo Pascal subroutines for asynchronous
; sound recording on the Tandy DAC. The Tandy DAC is assumed to be present;
; its address is obtained from the BIOS.
;
; To use this package, the main program calls start_record() to start, then
; writes out data as fullbuf flags become true, then calls stop_record() to
; stop recording. The main program should set the fullbuf flag off after
; writing it to disk or whatever; if the fullbuf flag is still set when the
; recording package gets back to the buffer, ovrflo is set to true and
; recording stops. The main program should watch that flag also. The main
; program should mark both buffers empty and neither buffer last and clear
; the ovrflo flag before calling start_record().
;
; These routines go directly to the hardware. The reason for that is to be
; able to stop recording instantaneously and report the number of bytes
; recorded into the last buffer. With Int 1Ah, one can only set the PSSJ
; to record a fixed number of bytes; although you can stop anytime, you can't
; tell how much has actually been recorded since the BIOS doesn't report
; that.
;
; start_record() takes two far pointers to buffers as parameters. The
; buffers are 32768 bytes in size. Each buffer is divided into two parts
; if necessary to ensure that neither part crosses a 64k boundary.
;
; start_record() does the following:
; converts buffer addresses to linear
; divides the buffers so that they do not cross 64k boundaries and
; determines the length of each part
; gets the DAC address from the BIOS
; sets the current input buffer to buffer0
; sets the number of bytes done to zero
; hooks Int 15h and 0Fh
; disables interrupts
; programs the DMA controller for sound input on the first part of the
; first buffer
; programs the sound chip to do input with DMA
; starts sound DMA
; enables interrupts
;
; The Int 0Fh handler does the following:
; if not a DMA EOP interrupt, jumps to default Int 0Fh handler
; adds the size of the buffer part done to the number of bytes done
; if the first part of a two-part buffer:
; starts DMA on the second part
; otherwise:
; sets lastbytes to the number of bytes done (32768)
; marks the buffer full
; sets the current input buffer to the other buffer
; if the current input buffer is marked full then:
; sets ovrflo to true
; otherwise:
; starts DMA on the first part of the current buffer
; issues EOI to the interrupt controller
;
; The Int 15h handler does the following:
; if AH <> 4Fh, jumps to default Int 15h handler
; if code in AL is not a make code, clears carry and exits
; if sound DMA is not in progress, clears carry and exits
; otherwise:
; stops sound DMA
; adds the current DMA channel 1 count to the number of bytes done
; marks the current buffer full
; marks the current buffer last
; sets lastbytes to the number of bytes done
; clears carry
;
; stop_record() does the following:
; stops sound DMA if in progress
; unhooks Int 15h and 0Fh
;
; The Turbo Pascal invocation syntax is:
;
; procedure start_record(
; divider: (* DAC divider for recording *)
; word;
; buffer0, (* pointer to first sound DMA buffer *)
; buffer1: (* pointer to second sound DMA buffer *)
; pointer;
; var lastbuf, (* "last buffer" flags *)
; fullbuf: (* "full buffer" flags *)
; bool2;
; var lastbytes: (* number of bytes in the last buffer *)
; word;
; var ovrflo: (* true if input overflow occurred *)
; boolean ); external;
;
; procedure stop_record; external;
;
CODE SEGMENT BYTE PUBLIC
ASSUME CS:CODE,DS:CODE
PUBLIC START_RECORD,STOP_RECORD
;
; Parameters for start_record().
;
OVRFLO EQU [BP+4]
LASTBYTES EQU [BP+8]
FULLBUF EQU [BP+12]
LASTBUF EQU [BP+16]
BUFFER1 EQU [BP+20]
BUFFER0 EQU [BP+24]
DIVIDER EQU [BP+28]
;
; Local variables.
;
OVRFLOPTR DD 0 ; pointer to overflow flag (byte)
LASTBYTESPTR DD 0 ; pointer to lastbytes count (word)
FULLBUFPTR DD 0 ; pointer to "full buffer" flag array (2 bytes)
LASTBUFPTR DD 0 ; pointer to "last buffer" flag array (2 bytes)
;
; "Buffers" in this package are DMA buffers, up to four of
; them. Page value of 0 indicates not used. Two of these
; buffers equal one for the main program.
;
CURRENTBUF DW 0 ; current buffer (0-3)
BUFADDRS DW 4 DUP (0) ; buffer start addresses for DMA
BUFPAGES DB 4 DUP (0) ; buffer page register values for DMA
BUFCOUNTS DW 4 DUP (0) ; buffer initial counts for DMA
;
; Some more locals.
;
DACADDR DW 0 ; base I/O port address of DAC
INT15DEFAULT DD 0 ; default Int 15h vector
INT0FDEFAULT DD 0 ; default Int 0Fh vector
INTMASK DB 0 ; default 8259A interrupt mask
;
; Number of bytes recorded since the last buffer was marked
; full.
;
BYTESDONE DW 0
;
; Int 0Fh service routine (DMA EOP).
;
; If not an end-of-process interrupt, jump to the default handler.
;
INT0FHDLR PROC FAR
PUSH AX
PUSH DX
MOV DX,CS:DACADDR
IN AL,DX
TEST AL,8
JNZ INT0F_EOP
POP DX
POP AX
JMP DWORD PTR CS:INT0FDEFAULT
;
; DMA EOP on channel 1.
;
INT0F_EOP: CLI
PUSH BX
PUSH SI
PUSH DI
PUSH DS
PUSH ES
MOV AX,CS ; DS addresses local data
MOV DS,AX
MOV AL,5 ; disable DMA channel 1
OUT 0Ah,AL
MOV DX,DACADDR ; clear interrupt
IN AL,DX
JMP $+2
AND AL,0F7h
OUT DX,AL
;
; Add count for buffer to number of bytes done.
;
MOV DI,CURRENTBUF ; get array index
SHL DI,1
MOV AX,BUFCOUNTS[DI]
INC AX
ADD AX,BYTESDONE
MOV BYTESDONE,AX
;
; If 32768 bytes have been done, mark the buffer full.
;
CMP AX,32768
JB INT0F_NOTDONE
SHR DI,1 ; DI = buffer0, buffer1
SHR DI,1
LES BX,FULLBUFPTR
MOV BYTE PTR ES:[BX+DI],1
XOR DI,1 ; go to next pair of buffers
CMP BYTE PTR ES:[BX+DI],1 ; check for overflow
JNE INT0F_NOOVRFLO
;
; Overflow occurred. Mark last buffer last and set ovrflo
; flag.
;
XOR DI,1 ; go back to buffer that overflowed
LES BX,LASTBUFPTR ; mark it last
MOV BYTE PTR ES:[BX+DI],1
LES BX,LASTBYTESPTR ; set LASTBYTES to 32768
MOV WORD PTR ES:[BX],32768
LES BX,OVRFLOPTR ; set overflow flag
MOV BYTE PTR ES:[BX],1
MOV DX,DACADDR ; stop sound DMA
IN AL,DX
JMP $+2
AND AL,0FBh
OUT DX,AL
JMP INT0F_EXIT
;
; Overflow did not occur. Set CURRENTBUF to address the
; next pair of buffers.
;
INT0F_NOOVRFLO: SHL DI,1
MOV CURRENTBUF,DI
MOV SI,DI
SHL DI,1
MOV BYTESDONE,0
JMP INT0F_RESTART
;
; Need to do the second part of the buffer pair.
;
INT0F_NOTDONE: MOV SI,CURRENTBUF
INC SI
MOV CURRENTBUF,SI
MOV DI,SI
SHL DI,1
;
; Restart DMA on the new buffer half. SI is a byte array
; index, DI is a word array index.
;
INT0F_RESTART: MOV AL,45h ; select channel 1, write transfer to memory,
OUT 0Bh,AL ; autoinitialization disabled, address incre-
JMP $+2 ; ment, single mode
MOV AL,BUFPAGES[SI]
OUT 83h,AL ; set DMA channel 1 page register
JMP $+2
MOV AL,0FFh ; clear byte pointer flip/flop
OUT 0Ch,AL
JMP $+2
MOV AX,BUFCOUNTS[DI]
OUT 03h,AL ; set DMA channel 1 count
JMP $+2
MOV AL,AH
OUT 03h,AL
JMP $+2
MOV AX,BUFADDRS[DI]
OUT 02h,AL ; set DMA channel 1 base address
JMP $+2
MOV AL,AH
OUT 02h,AL
MOV DX,DACADDR ; reenable sound chip EOP interrupt
IN AL,DX
JMP $+2
OR AL,8
OUT DX,AL
MOV AL,1 ; enable DMA channel 1
OUT 0Ah,AL
;
; Issue EOI, restore registers and exit.
;
INT0F_EXIT: MOV AL,20h
OUT 20h,AL
POP ES
POP DS
POP DI
POP SI
POP BX
;
POP DX
POP AX
IRET
INT0FHDLR ENDP
;
; Int 15h service routine (keyboard intercept).
;
; If not a keyboard intercept, jump to default handler.
;
INT15HDLR PROC FAR
CMP AH,4Fh
JNE INT15_INTRCEPT
JMP DWORD PTR CS:INT15DEFAULT
;
; If not a make code, clear carry and exit.
;
INT15_INTRCEPT: TEST AL,80h
JZ INT15_MAKE
JMP INT15_EXIT
;
; It's a make code. Check if sound DMA is in progress. If
; not, clear carry and exit.
;
INT15_MAKE: PUSH AX
PUSH DX
MOV DX,CS:DACADDR
IN AL,DX
TEST AL,4
JNZ INT15_INDMA
JMP INT15_POPDX
;
; Sound DMA in progress. Stop it.
;
INT15_INDMA: CLI
PUSH BX ; save other registers needed
PUSH SI
PUSH DI
PUSH DS
PUSH ES
AND AL,0FBh ; disable sound chip DMA
OUT DX,AL
MOV AX,CS ; DS addresses local data
MOV DS,AX
MOV SI,CURRENTBUF ; SI addresses caller's arrays
MOV DI,SI ; DI is word array index
SHR SI,1
SHL DI,1
MOV AL,5 ; disable DMA channel 1 while programming it
OUT 0Ah,AL
JMP $+2
MOV AL,0FFh ; clear byte pointer flip/flop
OUT 0Ch,AL
JMP $+2
IN AL,3 ; get DMA channel 1 count
JMP $+2
MOV AH,AL
IN AL,3
XCHG AL,AH ; AX = DMA channel 1 count (current)
SUB AX,BUFCOUNTS[DI]
NEG AX ; AX = number of bytes done since EOP
ADD AX,BYTESDONE ; AX = number of bytes in buffer0/1
LES BX,LASTBYTESPTR ; set # of bytes in last buffer
MOV ES:[BX],AX
LES BX,LASTBUFPTR ; mark buffer last
MOV BYTE PTR ES:[BX+SI],1
LES BX,FULLBUFPTR ; mark buffer full
MOV BYTE PTR ES:[BX+SI],1
POP ES
POP DS
POP DI
POP SI
POP BX
STI
;
; Pop DX, AX.
;
INT15_POPDX: POP DX
POP AX
;
; Clear carry and exit (thereby causing the BIOS to ignore
; the keystroke).
;
INT15_EXIT: CLC
RETF 2
INT15HDLR ENDP
;
; Subroutine, converts pointer in DX:AX to a linear address. Modifies AX,
; CX,DX.
;
TO_LINEAR PROC NEAR
MOV CL,4
ROL DX,CL
MOV CX,DX
AND CX,0FFF0h
AND DX,0Fh
ADD AX,CX
ADC DX,0
RET
TO_LINEAR ENDP
;
; Subroutine, takes the linear address of a buffer in DL:AX and its number
; (0 or 2) in SI, and fills in the buffer data arrays. Modifies AX,DX,SI,DI.
;
FIX_64K PROC NEAR
MOV DI,SI
SHL DI,1
MOV BUFADDRS[DI],AX ; save address of first part
MOV BUFPAGES[SI],DL
;
; If buffer is contained in one 64k segment, the second part
; is not used, and 32768 bytes are recorded into the first
; part.
;
CMP AX,32768
JA FIX_64K_HI
MOV BUFCOUNTS[DI],32767 ; DMA transfer 32768 bytes
MOV BUFPAGES[SI+1],0 ; zero page indicates not used
RET
;
; Otherwise, the buffer is split into two parts. The first
; part extends to the 64k boundary.
;
FIX_64K_HI: NOT AX ; = (65536 - AX) - 1
MOV BUFCOUNTS[DI],AX
MOV BUFADDRS[DI+2],0 ; offset 0 in next DMA page
INC DL
MOV BUFPAGES[SI+1],DL ; next DMA page
SUB AX,32766 ; = (32768 - (AX + 1)) - 1
NEG AX
MOV BUFCOUNTS[DI+2],AX
RET
FIX_64K ENDP
;
; Start routine.
;
START_RECORD PROC NEAR
PUSH BP ; save stack pointer
MOV BP,SP ; address parameters on stack
PUSH DS ; save DS
MOV AX,CS ; DS addresses local data
MOV DS,AX
;
; Copy pointers from stack to code segment for use by the
; interrupt handlers.
;
LES AX,OVRFLO
MOV WORD PTR OVRFLOPTR,AX
MOV WORD PTR OVRFLOPTR+2,ES
LES AX,LASTBYTES
MOV WORD PTR LASTBYTESPTR,AX
MOV WORD PTR LASTBYTESPTR+2,ES
LES AX,FULLBUF
MOV WORD PTR FULLBUFPTR,AX
MOV WORD PTR FULLBUFPTR+2,ES
LES AX,LASTBUF
MOV WORD PTR LASTBUFPTR,AX
MOV WORD PTR LASTBUFPTR+2,ES
;
; Get buffer0 pointer, convert to linear, and set buffer
; array data.
;
MOV AX,BUFFER0
MOV DX,BUFFER0+2
CALL TO_LINEAR
XOR SI,SI
CALL FIX_64K
;
; Same deal with buffer1.
;
MOV AX,BUFFER1
MOV DX,BUFFER1+2
CALL TO_LINEAR
MOV SI,2
CALL FIX_64K
;
; Get the base DAC port address and save it.
;
MOV AX,8100h
INT 1Ah
MOV DACADDR,AX
;
; Set first buffer to 0, number of bytes recorded to 0.
;
XOR AX,AX
MOV CURRENTBUF,AX
MOV BYTESDONE,AX
;
; Hook Int 0Fh and 15h.
;
MOV AX,350Fh
INT 21h
MOV WORD PTR INT0FDEFAULT,BX
MOV WORD PTR INT0FDEFAULT+2,ES
MOV AX,3515h
INT 21h
MOV WORD PTR INT15DEFAULT,BX
MOV WORD PTR INT15DEFAULT+2,ES
MOV AX,250Fh
MOV DX,OFFSET INT0FHDLR
INT 21h
MOV AX,2515h
MOV DX,OFFSET INT15HDLR
INT 21h
;
; Disable interrupts.
;
CLI
;
; Program DMA channel 1.
;
MOV AL,5 ; disable DMA channel 1 while programming it
OUT 0Ah,AL
JMP $+2
MOV AL,45h ; select channel 1, write transfer to memory,
OUT 0Bh,AL ; autoinitialization disabled, address incre-
JMP $+2 ; ment, single mode
MOV AL,BUFPAGES
OUT 83h,AL ; set DMA channel 1 page register
JMP $+2
MOV AL,0FFh ; clear byte pointer flip/flop
OUT 0Ch,AL
JMP $+2
MOV AX,BUFCOUNTS
OUT 03h,AL ; set DMA channel 1 count
JMP $+2
MOV AL,AH
OUT 03h,AL
JMP $+2
MOV AX,BUFADDRS
OUT 02h,AL ; set DMA channel 1 base address
JMP $+2
MOV AL,AH
OUT 02h,AL
;
; DMA still disabled. Set up sound chip; will start when
; DMA is enabled.
;
MOV DX,DACADDR
IN AL,DX
JMP $+2
AND AL,0E0h ; recording with DMA/interrupt
OR AL,16h
OUT DX,AL
ADD DX,2 ; set recording speed - volume setting
MOV AX,DIVIDER ; ignored when recording
OUT DX,AL
JMP $+2
INC DX
MOV AL,AH
OUT DX,AL
JMP $+2
MOV DX,DACADDR ; enable DMA EOP interrupt
IN AL,DX
JMP $+2
AND AL,0E0h
OR AL,1Eh
OUT DX,AL
;
; Save interrupt mask and enable IRQ 7.
;
IN AL,21h
JMP $+2
MOV INTMASK,AL
AND AL,7Fh
OUT 21h,AL
;
; Enable DMA channel 1 to get things going.
;
MOV AL,1
OUT 0Ah,AL
;
; Enable interrupts and exit.
;
STI
POP DS ; restore DS
POP BP ; restore stack pointer
RET
START_RECORD ENDP
;
; Stop routine. This routine may be called more than once, although calls
; after the first have no effect.
;
; Stop sound chip DMA.
;
STOP_RECORD PROC NEAR
PUSH DS
CLI
MOV DX,CS:DACADDR
IN AL,DX
JMP $+2
AND AL,0FBh
OUT DX,AL
STI
;
; Unhook Int 0Fh and Int 15h.
;
MOV AX,250Fh
LDS DX,CS:INT0FDEFAULT
INT 21h
MOV AX,2515h
LDS DX,CS:INT15DEFAULT
INT 21h
;
; Restore interrupt mask (disable IRQ 7 if disabled before).
;
CLI
MOV AL,CS:INTMASK
OUT 21h,AL
JMP $+2
STI
POP DS
RET
STOP_RECORD ENDP
CODE ENDS
END